#include "image.hpp"
#include "video.hpp"
#include "vec.hpp"
#include "data.hpp"

#include "SDL.h"
#ifdef GLES
#include <GLES/gl.h>
#include <GLES/glext.h>
#else
#include "SDL_opengl.h"
#endif
#include "SDL_image.h"

#include <string>
#include <iostream>

Image::Image()
{
	tex = 0;
	//texes = 0;
	cols = rows = 1;
	invcols = invrows = 1;
	tilew = tileh = 0;
	anchx = anchy = .5f;
}

Image::~Image()
{
	// if (opened)
	// 	Close();
}

static SDL_Surface* SurfFromText(const std::string & name, char *text, int len)
{
	char *error = NULL;

	SDL_RWops *rwo = SDL_RWFromMem(text, len);
	if(!rwo)
		error = SDL_GetError();
	
	SDL_Surface *surf = NULL;
	if (!error)
	{
		const bool AUTODEL = true;
		SDL_Surface *rawsurf = IMG_Load_RW(rwo, AUTODEL);
		if(!rawsurf)
			error = IMG_GetError();
		else
		{
			surf = SDL_DisplayFormatAlpha(rawsurf);
			SDL_FreeSurface(rawsurf);
		}
	}

	if (error)
	{
		std::cerr << "Image::Open(" << name << "): " << error;
		std::cerr << std::endl;
	}
	
	return surf;
}

#include <cstring>
#include <cstdio>

bool Image::Opened()
{
	return glIsTexture(tex);
	//return texes != 0;
}

#include "disk.hpp"

bool Image::Open(const std::string & name)
{
	if (Opened())
		return true;

	const char *DIR = "img/";
	const char *EXT = ".png";

	std::string path(DIR);
	path += name;
	path += EXT;

	//std::string text;
	//int len = Dsk.TextOpen(path, text);
	char *rawtext;
	int len = Dsk.CStrOpen(path, &rawtext);
	if (len <= 0)
		return false;

	SDL_Surface *imgsurf = SurfFromText(name, rawtext, len);
	SDL_SetAlpha(imgsurf, 0, SDL_ALPHA_OPAQUE);	// cut off SDL_SRCALPHA
	Dsk.CStrClose(rawtext);

	if (!imgsurf)
	{	
		return false;
	}

	const char *INFOEXT = ".info";
	std::string infopath(DIR);
	infopath += name;
	infopath += INFOEXT;
	
	std::string text;
	int readct = 0;
	if (Dsk.FExists(infopath.c_str()))
	{		
		Data imgdat;
		if (imgdat.FOpen(infopath))
		{
			for (Data d = imgdat.Begin(); d; ++d)
			{
				if (d.Named("cols"))
				{
					cols = d.Int();
					++readct;
				}
				else if (d.Named("rows"))
				{
					rows = d.Int();
					++readct;
				}
				else if (d.Named("anchx"))
				{
					anchx = d.Float();
					++readct;
				}
				else if (d.Named("anchy"))
				{
					anchy = d.Float();
					++readct;
				}
			}
			imgdat.Close();
		}
	}

	if (readct < 2)
		cols = rows = 1;
	
	invcols = 1.f / cols;
	invrows = 1.f / rows;

	const float UNIT_PER_PIX = 1.f / Video::PIX_PER_UNIT;
	Uint16 tilepw = imgsurf->w / cols;
	Uint16 tileph = imgsurf->h / rows;
	tilew = UNIT_PER_PIX * tilepw;
	tileh = UNIT_PER_PIX * tileph;

	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
//	texes = new unsigned int[TileCt()];
//	glGenTextures(TileCt(), texes);
//	SDL_Surface *tilesurf = SDL_CreateRGBSurface(0, 
//			tilepw, tileph, 
//			imgsurf->format->BitsPerPixel,
//			imgsurf->format->Rmask,
//			imgsurf->format->Gmask,
//			imgsurf->format->Bmask,
//			imgsurf->format->Amask
//			);
//	
//
//	SDL_Rect imgrect;
//	imgrect.x = 0;
//	imgrect.y = 0;
//	imgrect.w = tilepw;
//	imgrect.h = tileph;
//
//	for (int t = 0; t < TileCt(); ++t)
//	{
//		glBindTexture(GL_TEXTURE_2D, texes[t]);

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

//		imgrect.x = tilepw * (t % cols);
//		imgrect.y = tileph * (t / cols);
//
//		SDL_BlitSurface(imgsurf, &imgrect, tilesurf, 0);
		
	unsigned int texintfmt, texfmt;
#ifdef GLES
	texintfmt = texfmt = GL_BGRA_EXT;
#else
	texintfmt = imgsurf->format->BytesPerPixel;
	texfmt = GL_BGRA;
#endif

	glTexImage2D(GL_TEXTURE_2D, 0, texintfmt,
			imgsurf->w, imgsurf->h, 0,
			texfmt, GL_UNSIGNED_BYTE, imgsurf->pixels);
//	}

//	SDL_FreeSurface(tilesurf);
	SDL_FreeSurface(imgsurf);

	//std::cout << "opened " << path << std::endl;
	return true;
}

void Image::Close()
{
	if (Opened())
	{
		//glDeleteTextures(TileCt(), texes);
		//delete [] texes;
		tex = 0;
	}
}
/*
void Image::GridDraw(const wchar_t *grid, int gcols, int grows)
{
	int c1 = 0;//std::max<int>(0, (int)(-left) / tilew);
	int r1 = 0;//std::max<int>(0, (int)(-top) / tileh);
	int c2 = gcols;//std::min<int>(c1 + (Video::WIDTH / tilew) + 2, gcols);
	int r2 = grows;//std::min<int>(r1 + (Video::HEIGHT / tileh) + 2, grows);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, tex);

	float vy = 0;

	for (int r = r1; r < r2; ++r)
	{
		// Not sure we can afford this convenience
		// RowDraw(&grid[(r * gcols) + c1], c2 - c1);
		// glTranslatef(0, tileh, 0);

		float vx = 0;

		glBegin(GL_TRIANGLE_STRIP);

		for (int c = c1; c < c2; ++c)
		{
			int tile = grid[(r * gcols) + c];

			float tx = invcols * TileCol(tile);
			float ty = invrows * TileRow(tile);

			glTexCoord2f(tx, ty);
			glVertex2f(vx, vy);

			glTexCoord2f(tx, ty + invrows);
			glVertex2f(vx, vy + tileh);

			glTexCoord2f(tx + invcols, ty);
			glVertex2f(vx + tilew, vy);

			glTexCoord2f(tx + invcols, ty + invrows);
			glVertex2f(vx + tilew, vy + tileh);

			vx += tilew;
		}

		glEnd();

		vy += tileh;
	}
}
*/
void Image::RowDraw(const wchar_t *row, int n, const Matf &transform)
{
	glBindTexture(GL_TEXTURE_2D, tex);

	const int VERT_PER_TILE = 6;
	const int VC_PER_TILE = 2 * VERT_PER_TILE;
	const int MAX_ROW_TILES = 240;
	enum
	{
		LTX,	LTY,
		LBX,	LBY,
		RTX,	RTY,
		RBX,	RBY,
		RTX2,	RTY2,
		LBX2,	LBY2
	};
	n = std::min(n, MAX_ROW_TILES);

	static float tc[VC_PER_TILE * MAX_ROW_TILES];
	static float vt[VC_PER_TILE * MAX_ROW_TILES];

	glTexCoordPointer(2, GL_FLOAT, 0, tc);
	glVertexPointer(2, GL_FLOAT, 0, vt);

	int vertct = 0;
	for (int c = 0; c < n; ++c)
	{
		short tile = row[c];
		if (!tile)
		{
			continue;
		}

		float *tilevt = &vt[VC_PER_TILE*vertct];
		tilevt[LTX] = transform.TransformX(c*tilew, 0);
		tilevt[LTY] = transform.TransformY(c*tilew, 0);
		tilevt[LBX] = transform.TransformX(c*tilew, tileh);
		tilevt[LBY] = transform.TransformY(c*tilew, tileh);
		tilevt[RTX] = transform.TransformX((c+1)*tilew, 0);
		tilevt[RTY] = transform.TransformY((c+1)*tilew, 0);
		tilevt[RBX] = transform.TransformX((c+1)*tilew, tileh);
		tilevt[RBY] = transform.TransformY((c+1)*tilew, tileh);
		tilevt[RTX2] = tilevt[RTX];
		tilevt[RTY2] = tilevt[RTY];
		tilevt[LBX2] = tilevt[LBX];
		tilevt[LBY2] = tilevt[LBY];

		float tx = invcols * TileCol(std::abs(tile));
		float ty = invrows * TileRow(std::abs(tile));
		float *tiletc = &tc[VC_PER_TILE*vertct];
		if (tile < 0)
		{
			tiletc[LTX] = tx + invcols;
			tiletc[RTX] = tx;
		}
		else
		{
			tiletc[LTX] = tx;
			tiletc[RTX] = tx + invcols;
		}
		tiletc[LBX] = tiletc[LBX2] = tiletc[LTX];
		tiletc[RBX] = tiletc[RTX2] = tiletc[RTX];
		tiletc[LTY] = tiletc[RTY] = tiletc[RTY2] = ty;
		tiletc[LBY] = tiletc[RBY] = tiletc[LBY2] = ty + invrows;
		++vertct;
	}

	glDrawArrays(GL_TRIANGLES, 0, VERT_PER_TILE * vertct);
}

void Image::Draw(int tile, const Matf &transform)
{
	glBindTexture(GL_TEXTURE_2D, tex);

	float left = tilew * -anchx;
	float right = left + tilew;
	float top = tileh * -anchy;
	float bottom = top + tileh;

	float tc[8];
        
	float vt[8] = 
	{
		transform.TransformX(left,top),
		transform.TransformY(left,top),
		transform.TransformX(left,bottom),
		transform.TransformY(left,bottom),
		transform.TransformX(right,top),
		transform.TransformY(right,top),
		transform.TransformX(right,bottom),
		transform.TransformY(right,bottom)
	};

	float tx = invcols * TileCol(std::abs(tile));
	float ty = invrows * (TileRow(std::abs(tile)) % rows);
	if (tile < 0)
	{
		tc[0] = tc[2] = tx + invcols;
		tc[4] = tc[6] = tx;
	}
	else
	{
		tc[0] = tc[2] = tx;
		tc[4] = tc[6] = tx + invcols;
	}

	tc[1] = tc[5] = ty;
	tc[3] = tc[7] = ty + invrows;

	glTexCoordPointer(2, GL_FLOAT, 0, tc);
	glVertexPointer(2, GL_FLOAT, 0, vt);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

void Image::BoxDraw(const Matf & transform)
{
	float left = tilew * -anchx;
	float right = left + tilew;
	float top = tileh * -anchy;
	float bottom = top + tileh;

	float vt[8] = 
	{
		transform.TransformX(left,top),
		transform.TransformY(left,top),
		transform.TransformX(left,bottom),
		transform.TransformY(left,bottom),
		transform.TransformX(right,bottom),
		transform.TransformY(right,bottom),
		transform.TransformX(right,top),
		transform.TransformY(right,top)
	};

	glVertexPointer(2, GL_FLOAT, 0, vt);
	glDrawArrays(GL_LINE_LOOP, 0, 4);
}

int Image::TileAtPoint(float x, float y)
{
	int col = (int)(x / tilew);
	int row = (int)(y / tileh);
	Clamp(col, row);
	return Tile(col, row);
}

#include <algorithm>

void Image::Clamp(int & col, int & row)
{
	col = std::max<int>(0, std::min<int>(col, cols - 1));
	row = std::max<int>(0, std::min<int>(row, rows - 1));
}

Assets<Image> AllImgs;
